package Networking;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.net.SocketException;
import java.util.Arrays;

import Application.WorkUnit;

public class NetworkHandler implements Runnable {
	private Socket socket;
	private ObjectInputStream objectIn;
	private ObjectOutputStream objectOut;
	private boolean shouldContinue;
	private byte[] MacAddress;
	private int clientID;
	private boolean isClient;
	private INetworkServer server;
	private INetworkClient client;

	 private static String serverLocation = "localhost";
//	private static String serverLocation = "67.172.235.149";
	private static int PORT = 1337;

	/**
	 * Constructor for creating a NetworkingHandler on the server side. This
	 * version doesn't start with the hello handshake, and goes straight to
	 * listening.
	 * 
	 * @param socket
	 * @param server
	 */
	public NetworkHandler(Socket socket, INetworkServer server) {
		this.socket = socket;
		this.server = server;
		isClient = false;

		initialize();
	}

	/**
	 * Constructor for creating a NetorkingHandler on the client side. This
	 * version starts with the hello handshake, letting the server side know
	 * what its ID is.
	 * 
	 * @param clientID
	 */
	public NetworkHandler(int clientID, INetworkClient client) {
		this.clientID = clientID;
		this.client = client;
		isClient = true;

		initialize();
	}

	private void initialize() {
		shouldContinue = true;

		try {
			if (isClient)
				socket = new Socket(serverLocation, PORT);
			objectOut = new ObjectOutputStream(socket.getOutputStream());
			objectIn = new ObjectInputStream(socket.getInputStream());

			NetworkInterface networkInterface = NetworkInterface
					.getByInetAddress(InetAddress.getLocalHost());
			MacAddress = networkInterface.getHardwareAddress();
		} catch (IOException e) {
			e.printStackTrace();
		}

		new Thread(this).start();
	}

	public void run() {
		if (isClient)
			doHelloHandshake();

		while (shouldContinue) {
			Object object = readObject();

			if (object != null) {
				if (object instanceof INetMessage) {
					if (object instanceof CloseSocket) {
						shouldContinue = false;
					} else if (object instanceof NewWorkUnit) {
						doNewWorkUnit(object);
					} else if (object instanceof NewClient) {
						doNewClient(object);
					} else if (object instanceof NewClientConfirm) {
						doNewClientConfirm(object);
					} else if (object instanceof FinishedWorkUnit) {
						doFinishedWorkUnit(object);
					}
				} else {
					System.err
							.println("[NetorkHandler] Error: Read object not of type NetMessage!");
					System.err.println("Was of type: " + object.getClass());
				}
			}
		}

		closeSocket();
	}
	
	private void doFinishedWorkUnit(Object object) {
		FinishedWorkUnit finishedWorkUnit = (FinishedWorkUnit) object;
		server.finishedWorkUnit(finishedWorkUnit.getWorkUnit());
	}
	
	private void doNewClientConfirm(Object object) {
		NewClientConfirm newClientConfirm = (NewClientConfirm)object;
		client.setID(newClientConfirm.getClientID());
	}

	/**
	 * Alerts the parent server that a new client connected, and gives the
	 * clientID.
	 * 
	 * @param object
	 */
	private void doNewClient(Object object) {
		NewClient newClient = (NewClient) object;
		server.gotNewClient(newClient.getClientID());
	}

	private void doHelloHandshake() {
		INetMessage newClient = new NewClient(clientID);
		try {
			objectOut.writeObject(newClient);
			objectOut.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void doNewWorkUnit(Object object) {
		NewWorkUnit newWorkUnit = (NewWorkUnit) object;
		WorkUnit workUnit = newWorkUnit.getWorkUnit();

		client.gotWorkUnit(workUnit);

		// TODO [NetworkHandler:doNewWorkUnit] Add new work unit code here
	}

	private Object readObject() {
		Object object = null;
		try {
			object = objectIn.readObject();
		} catch (SocketException | EOFException e) {
			// Socket was forcedly closed. Probably means client was
			// disconnected
			System.out.println("[NetworkHandler] SOCKET CLOSED");
			shouldContinue = false;
			if (!isClient)
				server.clientDisconnected(clientID);
		} catch (ClassNotFoundException | IOException e) {
			// If shouldContinue is true, we haven't said that we want to close
			// the connection
			if (shouldContinue) {
				e.printStackTrace();
				System.err
						.println("[NetworkHandler] Error: Couldn't read object correctly");
			}
		}
		return object;
	}

	private void closeSocket() {
		try {
			socket.close(); // TODO [NetworkHandler:closeSocket()] Check this is
							// done correctly
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void closeConnection() {
		shouldContinue = false;

		try {
			objectOut.writeObject(new CloseSocket());
			objectOut.flush();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void sendNewWorkUnit(WorkUnit workUnit) {
		try {
			objectOut.writeObject(new NewWorkUnit(workUnit));
			objectOut.flush();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void sendNewClientConfirm(int clientID) {
		try {
			objectOut.writeObject(new NewClientConfirm(clientID));
			objectOut.flush();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void sendFinishedWorkUnit(WorkUnit workUnit) {
		try {
			objectOut.writeObject(new FinishedWorkUnit(workUnit));
			objectOut.flush();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void testCloseSocket() {
		try {
			socket.close();
		} catch (IOException e) {
			System.err
					.println("[NetworkHandler] Closed to socket. This should only happen in testing");
		}
	}

}
